﻿using System;
using System.Data;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using GoldDigger.Models;
using GoldDigger.AppServices;
using GoldDigger.Data;

namespace GoldDigger.Processor
{
    public class ProblemProcessor
    {
        public DataHolder AppData { get; set; }

        public ProblemProcessor(DataHolder dh)
        {
            AppData = dh;
        }

        #region ExportResults

        public void ExportResults(string path, AssociationRuleModel am)
        {
            StreamWriter sw = new StreamWriter(path);
            var counter = 1;
            var counter2 = 1;

            //Basic gexf format that will be the same regardless of association rules
            sw.WriteLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            sw.WriteLine("<gexf xmlns=\"http://www.gexf.net/1.1draft\" version=\"1.1\" xmlns:viz=\"http://www.gexf.net/1.1draft/viz\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.gexf.net/1.1draft http://www.gexf.net/1.1draft/gexf.xsd\">");
            sw.WriteLine("<graph defaultedgetype=\"directed\" timeformat=\"double\" mode=\"dynamic\">");
            sw.WriteLine("<attributes class=\"node\" mode=\"static\">");
            sw.WriteLine("<attribute id=\"0\" title=\"Confidence\" type=\"float\"></attribute>");
            sw.WriteLine("</attributes>");
            sw.WriteLine("<attributes class=\"edge\" mode=\"static\">");
            sw.WriteLine("<attribute id=\"0\" title=\"SupportCount\" type=\"integer\"></attribute>");
            sw.WriteLine("</attributes>");
            sw.WriteLine("<nodes>");

            //foreach item on the left hand side of the rule create a node
            foreach (var x in am.Lhs.ItemList)
            {
                sw.WriteLine("<node id=\""+counter+"\" label=\""+x +"\">");
                sw.WriteLine("</node>");
                counter++;
            }

            //Do the same for the right hand side of the rule
            foreach (var y in am.Rhs.ItemList)
            {
                sw.WriteLine("<node id=\"" + counter + "\" label=\"" + y + "\">");
                sw.WriteLine("</node>");
                counter++;
            }

            //Create a node representing the confidence value for the rule
            sw.WriteLine("<node id=\"" + counter + "\" label=\"\">");
            sw.WriteLine("<attvalue for=\"0\" value=\"0.54\"></attvalue>");
            sw.WriteLine("</node>");

            sw.WriteLine("</nodes>");
            sw.WriteLine("<edges>");

            //Next two for loops connect all the nodes via appropriate edges
            foreach (var z in am.Lhs.ItemList)
            {
                sw.WriteLine("<edge source=\""+counter2+"\" target=\""+counter+"\" weight=\"1.0\">");
                sw.WriteLine("<attvalue for=\"0\" value=\""+am.Lhs.SupportCount+"\"></attvalue>");
                sw.WriteLine("</edge>");
                counter2++;
            }

            foreach (var c in am.Rhs.ItemList)
            {
                sw.WriteLine("<edge source=\"" + counter + "\" target=\"" + counter2 + "\" weight=\"1.0\">");
                sw.WriteLine("<attvalue for=\"0\" value=\"" + am.Rhs.SupportCount + "\"></attvalue>");
                sw.WriteLine("</edge>");
                counter2++;
            }

            sw.WriteLine("</edges>");
            sw.WriteLine("</graph>");
            sw.WriteLine("</gexf>");

            sw.Close();
        }

        #endregion

        #region ImportData
        public DataTable PedMapToDT(SourceFileModel fileInfo, bool control)
        {
            StreamReader mapReader = new StreamReader(fileInfo.MapFile);
            StreamReader pedReader = new StreamReader(fileInfo.PedFile);

            string line;
            char currentChar, nextChar, secondChar = ' ';
            char[] preGenotype = new char[31];
            int columnCount = 1;
            string preDataString, labelName, category;
            int intChar = -1;
            int newLineCount = 0;
            DataTable dt = new DataTable();
            DataRow dr = dt.NewRow();

            dt.Columns.Add("Sample ID", typeof(string));

            while ((line = mapReader.ReadLine()) != null)   
            {
                char[] delimiters = new char[] { '\t' };
                string[] parts = line.Split(delimiters, StringSplitOptions.RemoveEmptyEntries);
                dt.Columns.Add(parts[1], typeof(string));
                AppData.AttributeList.Add(new AttributeModel(parts[1], false));
            }

            dt.Columns.Add("Category", typeof(int));
 
            //If there are control cases, cycle through the data until you reach the start of the proper stuff
            //There are 17 control cases in the FinalGENIE file.
            if (control)
            {
                while (newLineCount != fileInfo.ControlCount)
                {
                    if (pedReader.Read() == '\n')
                    {
                        newLineCount++;
                    }
                }
            }

            pedReader.Read(preGenotype, 0, preGenotype.Length);
            preDataString = new string(preGenotype);
            labelName = preDataString.Substring(15, 7);
            category = preDataString.Substring(29, 1);

            while (pedReader.Peek() >= 0)
            {
                dr[0] = labelName;

                intChar = pedReader.Read();
                currentChar = Convert.ToChar(intChar);
                char[] genoPair = new char[2];
                string genoString = "";

                if (currentChar == 'A' || currentChar == 'C' || currentChar == 'G' || currentChar == 'T')
                {
                    intChar = pedReader.Read();
                    nextChar = Convert.ToChar(intChar);
                    genoPair[0] = currentChar;

                    if (nextChar == ' ')
                    {
                        intChar = pedReader.Read();
                        secondChar = Convert.ToChar(intChar);
                        if (secondChar == 'A' || secondChar == 'C' || secondChar == 'G' || secondChar == 'T')
                        {
                            genoPair[1] = secondChar;
                            genoString = new string(genoPair);
                            dr[columnCount] = genoString;
                            columnCount++;
                        }
                    }
                }
                else if (currentChar == '0')
                {
                    intChar = pedReader.Read();
                    nextChar = Convert.ToChar(intChar);
                    if (nextChar == ' ')
                    {
                        intChar = pedReader.Read();
                        secondChar = Convert.ToChar(intChar);
                        if (secondChar == '0')
                        {
                            dr[columnCount] = "?";
                            columnCount++;
                        }
                    }
                }

                if (currentChar == '\n')
                {
                    dr[columnCount] = category;
                    dt.Rows.Add(dr);
                    dr = dt.NewRow();

                    columnCount = 1;
                    pedReader.Read(preGenotype, 0, preGenotype.Length);
                    preDataString = new string(preGenotype);
                    labelName = preDataString.Substring(15, 7);
                    category = preDataString.Substring(29, 1);

                    dr[0] = labelName;
                }
            }

            mapReader.Close();
            pedReader.Close();

            return dt;
        }
        #endregion

        #region FCBF
        public double CalculateSU(string colName1, string colName2)
        {
            var su = 0d;

            var infoGain = InformationGain(colName1, colName2);
            var entropy1 = Entropy(colName1);
            var entropy2 = Entropy(colName2);

            su = 2*(infoGain/(entropy1 + entropy2));

            return su;

        }

        public double Entropy(string colName)
        {
            //Get all distinct values in colName
            var valList = (from r in AppData.SourceDT.AsEnumerable() select r[colName]).Distinct().ToList();
            var entropy = 0d;

            //Foreach distinc value in colName
            foreach (var o in valList)
            {
                var count = 0;
                var allValues = (from r in AppData.SourceDT.AsEnumerable() select r[colName]).ToList();

                //Foreach specific variable in original colName
                foreach (var j in allValues)
                {
                    if (j.Equals(o))
                        count++;
                }
                
                //From loop above we know how many occurences of the current distinct value there are
                //Use this to calculate that distinct value's probabilty
                var prob = (double)count/(double)allValues.Count;
                entropy += prob*Math.Log(prob, 2);
            }

            return -1*entropy;
        }

        public double ConditionalEntropy(string colName1, string colName2)
        {
            //Get all distinct values in second list (this list is the conditional part - so List1 given List2)
            var valList1 = DataColumnToList(colName2).Distinct();
            //Get all distinct values in first list (this list contains the values we want to calculate the conEnt for)
            //var valList2 = colName1.Distinct();
            var conEntropy = 0d;

            //Foreach distinct value in List2
            foreach (var i in valList1)
            {
                //Counter and list of all values in the given column
                var countList2 = 0;
                var countList1 = 0;
                var entGiven = 0d;

                var allValuesIn2 = DataColumnToList(colName2);

                //List of all values in original list where value in given list is i
                var givenList =
                        (from r in AppData.SourceDT.AsEnumerable() where r[colName2].ToString() == i select r[colName1]).ToList();

                var distinctValues = givenList.Distinct();

                foreach (var j in allValuesIn2)
                {
                    if (j.Equals(i))
                    {   
                        countList2++;
                    }
                }

                foreach (var k in distinctValues)
                {
                    foreach (var l in givenList)
                    {
                        if (l.Equals(k))
                        {
                            countList1++;
                        }
                    }

                    var prob = (double)countList1 / (double)givenList.Count;
                    entGiven += prob * Math.Log(prob, 2);
                }

                //Calculate probability of current distinct value in colName2
                var probList2Val = (double)countList2 / (double)allValuesIn2.Count;

                conEntropy += (probList2Val*entGiven);

            }

            return -1*conEntropy;
        }

        public double InformationGain(string colName1, string colName2)
        {
            var entropy = Entropy(colName1);
            var conEntropy = ConditionalEntropy(colName1, colName2);
            var infoGain = entropy - conEntropy;

            return infoGain;
        }

        public List<string> DataColumnToList(string columnId)
        {
            var list = new List<string>();
            foreach (DataRow row in AppData.SourceDT.Rows)
            {
                list.Add(row[columnId].ToString());
            }
            return list;
        }

        #endregion

        #region Apriori

        public void GenerateLarge1Itemsets()
        {
            //Generate large 1-item itemset
            for(int i = 1; i < AppData.CurrentlySelectedDT.Columns.Count; i++)
            {
                //Get a list of values for the current column
                var sourceList = new List<string>();
                foreach (var sl in AppData.AprioriData.DataTableStringList)
                {
                    foreach (var s in sl)
                    {
                        if (s.Contains(AppData.CurrentlySelectedDT.Columns[i].ColumnName))
                            sourceList.Add(s);
                    }
                }

                //Get distinct values in list
                var tmpList = sourceList.Distinct();

                //Calculate support of each item on list, if greater than min specified value
                //Add to large itemset list
                foreach (var o in tmpList)
                {
                    var tmpSl = new List<string>();
                    tmpSl.Add(o);
                    var tmpModel = new ItemsetModel(tmpSl);

                    if (!AppData.AprioriData.LargeItemsets.Contains(tmpModel) || !AppData.AprioriData.NonLargeItemsets.Contains(tmpModel))
                    {
                        CalculateSupport(tmpModel);

                        if ((tmpModel.SupportVal >= AppData.AprioriData.Support))
                            AppData.AprioriData.LargeItemsets.Add(tmpModel);
                        else
                            AppData.AprioriData.NonLargeItemsets.Add(tmpModel);
                    }
                }
            }
        }

        public List<ItemsetModel> GetCandidateSeeds(List<ItemsetModel> list, int k)
        {
            var tmpList = new List<ItemsetModel>();

            foreach (ItemsetModel l in list)
            {
                if (l.ItemList.Count == k)
                    tmpList.Add(l);
            }

            return tmpList;
        }

        public List<ItemsetModel> GetCandidateItems(List<ItemsetModel> list)
        {
            var tmpList = new List<ItemsetModel>();

            for (int i = 0; i < list.Count - 1; i++)
            {
                for (int j = i + 1; j < list.Count; j++)
                {
                    foreach (string s in list[j].ItemList)
                    { 
                        if (!list[i].ItemList.Contains(s))
                        {
                            var tmpList2 = new List<string>(list[i].ItemList);
                            tmpList2.Add(s);

                            tmpList.Add(new ItemsetModel(tmpList2));
                        }
                    }
                }
            }

            foreach (ItemsetModel s in tmpList)
            {
                foreach (ItemsetModel sl in AppData.AprioriData.NonLargeItemsets)
                {
                    if (!s.ItemList.Except(sl.ItemList).Any())
                    {
                        tmpList.Remove(s);
                        break;
                    }
                }
            }

            return tmpList;
        }

        public void CalculateSupport(ItemsetModel model)
        {
            int itemSupport = 0;

            foreach (List<string> list2 in AppData.AprioriData.DataTableStringList)
            {
                if (!model.ItemList.Except(list2).Any())
                    itemSupport++;
            }

            model.SupportCount = itemSupport;
            model.SupportVal = ((double)itemSupport / (double)AppData.AprioriData.DataTableStringList.Count);
        }

        public double CalculateConfidence(ItemsetModel model, int sourceSupport)
        {
            var conf = 0d;
            var itemSupport = 0;

            foreach (ItemsetModel im in AppData.AprioriData.LargeItemsets)
            {
                if (model.ItemList.Count == im.ItemList.Count)
                {
                    var sameList = true;

                    for (int i = 0; i < model.ItemList.Count; i++)
                    {
                        //If strings differ, then lists are not same and break
                        if (!(model.ItemList[i] == im.ItemList[i]))
                        {
                            sameList = false;
                            break;
                        }
                    }

                    if (sameList)
                    {
                        itemSupport = im.SupportCount;
                        break;
                    }
                }
            }
            if (itemSupport == 0)
                conf = 0d;
            else
                conf = ((double)sourceSupport / (double)itemSupport);
            return conf;
        }
        #endregion 
    }
}


